﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Kiwi.Container.Caching;
using Kiwi.Container.DependencyInjection;
using Kiwi.Container.Lifetime;
using Kiwi.Container.Scope;

namespace Kiwi.Container.Internal
{
    [DebuggerDisplay("Container, scope={Scope.Discriminator}")]
    public class Container : IContainer, IInternalContainer
    {
        private readonly List<ActivatedComponent> _activatedComponents = new List<ActivatedComponent>();
        private readonly IComponentCache _cache = new ComponentCache();
        private readonly HashSet<Type> _componentTypes;
        private readonly ILifetimePolicy _lifetimePolicy;
        private readonly Container _parent;
        private readonly object _syncObject = new object();

        public Container(IContainerScope scope, IEnumerable<IContainerExtension> extensions,
                         ILookup<Type, IComponentRegistration> creatorLookup)
            : this(
                null, scope,
                new CompositeLifetimePolicy(extensions.Select(ext => ext as ILifetimePolicy).Where(p => p != null)),
                extensions, creatorLookup)
        {
        }

        protected Container(Container parent, IContainerScope scope, ILifetimePolicy lifetimePolicy,
                            IEnumerable<IContainerExtension> extensions,
                            ILookup<Type, IComponentRegistration> creatorLookup)
        {
            _parent = parent;
            Scope = scope;
            _lifetimePolicy = lifetimePolicy;
            Extensions = extensions;
            CreatorLookup = creatorLookup;
            _componentTypes =
                new HashSet<Type>(CreatorLookup.SelectMany(l => l).Select(c => c.ComponentType.Type).Distinct());
        }

        public IContainerScope Scope { get; set; }

        #region IContainer Members

        public IContainer Parent
        {
            get { return _parent; }
        }

        public IContainer CreateContainer(IContainerScope scope)
        {
            return new Container(this, scope, _lifetimePolicy, Extensions, CreatorLookup);
        }

        public IEnumerable<IContainerExtension> Extensions { get; private set; }

        public bool HasComponent(Type type)
        {
            return _componentTypes.Contains(type);
        }

        public object TryResolve(Type type, Func<IComponentRegistration, bool> filter)
        {
            return CreateResolver().TryResolve(type, filter);
        }

        public object Resolve(Type type, Func<IComponentRegistration, bool> filter)
        {
            return CreateResolver().Resolve(type, filter);
        }

        public IEnumerable<object> ResolveAll(Type type, Func<IComponentRegistration, bool> filter)
        {
            return CreateResolver().ResolveAll(type, filter);
        }

        public void Inject(object instance, IEnumerateDependentProperties dependentProperties)
        {
            CreateResolver().Inject(instance, dependentProperties);
        }

        public object Construct(Type type, IEnumerateDependentProperties dependentProperties)
        {
            return CreateResolver().Construct(type, dependentProperties);
        }

        public void Dispose()
        {
            foreach (var component in _activatedComponents)
            {
                component.Release(_lifetimePolicy);
            }
        }

        #endregion

        #region IInternalContainer Members

        bool IComponentCache.TryGetComponent(IComponentRegistration key, out object component)
        {
            var realCache = key.CacheResolver.GetCache(_cache);
            return realCache.TryGetComponent(key, out component);
        }

        void IComponentCache.AddComponent(IComponentRegistration key, object component)
        {
            var activatedComponent = new ActivatedComponent(component, key);
            activatedComponent.Activate(_lifetimePolicy);
            _activatedComponents.Add(activatedComponent);

            var actualCache = key.CacheResolver.GetCache(_cache);
            actualCache.AddComponent(key, component);
        }

        IInternalContainer IInternalContainer.InternalParent
        {
            get { return _parent; }
        }

        IContainerScope IInternalContainer.Scope
        {
            get { return Scope; }
        }

        public ILookup<Type, IComponentRegistration> CreatorLookup { get; private set; }

        IInternalContainerLock IInternalContainer.CreateLock()
        {
            return new ContainerLock(_syncObject);
        }

        #endregion

        private IComponentResolver CreateResolver()
        {
            return new ComponentResolver(this);
        }

        #region Nested type: ActivatedComponent

        private class ActivatedComponent
        {
            private readonly object _component;
            private readonly IComponentRegistration _registration;

            public ActivatedComponent(object component, IComponentRegistration registration)
            {
                _component = component;
                _registration = registration;
            }

            public void Activate(ILifetimePolicy activator)
            {
                _registration.LifetimeManager.StartLife(activator, _component);
            }

            public void Release(ILifetimePolicy activator)
            {
                _registration.LifetimeManager.EndLife(activator, _component);
            }
        }

        #endregion

        #region Nested type: ContainerLock

        private class ContainerLock : IInternalContainerLock
        {
            private readonly object _syncObject;

            public ContainerLock(object syncObject)
            {
                _syncObject = syncObject;
                Monitor.Enter(_syncObject);
            }

            #region IInternalContainerLock Members

            public void Dispose()
            {
                Monitor.Exit(_syncObject);
            }

            #endregion
        }

        #endregion
    }
}